Jako optimßlnφ k≤d bych oznaΦil takov²
zßpis programu, kter² nemß ₧ßdnΘ zßva₧n∞jÜφ nedostatky ve svΘm v²konu, p°esto₧e
obecn∞ panuje domn∞nka, ₧e optimßlnφ rovnß se nejrychlejÜφ. Je to v∞c Φist∞
subjektivnφ a zßle₧φ p°edevÜφm na tom, co od programu oΦekßvßme. Malß utilitka
nebo st°edn∞ velkß aplikace nejspφÜ v∞tÜφ optimalizaci k≤du pot°ebovat nebude,
opakem m∙₧e b²t nap°φklad 3D aplikace. Pokud dojde na optimalizaci, je dobrΘ
vytyΦit si jasn² cφl a v∞d∞t kdy s nφ skonΦit, nap°φklad pokud si programßtor
urΦφ 28 fps ve svΘ h°e.
Rychlost vs. ╚itelnost
Mnoho lidφ se domnφvß, ₧e s rychl²m k≤dem jde
ruku v ruce oÜkliv² a neΦiteln² k≤d. V n∞kter²ch p°φpadech tomu tak opravdu b²vß
a vyhnout se tomu dß jen t∞₧ko, p°esto vÜak obecn∞ platφ, ₧e takov² k≤d m∙₧e b²t
nejen dob°e Φiteln², ale dokonce i velmi elegantnφ.
┌rove≥ optimalizace
P°i psanφ slo₧it∞jÜφho programu jste urΦit∞ n∞kdy
narazili na to, ₧e napsan² algoritmus je pro vaÜe pot°eby p°φliÜ pomal². Je
t°eba rozhodnout, zda je dan² algoritmus ten vhodn², a p°φpadn∞ se poohlΘdnout
po jinΘm. Optimalizace mß obecn∞ dv∞ ·rovn∞ - optimalizace na ·rovni algoritmu a
na ·rovni implementace algoritmu. Jestli₧e si jste jisti, ₧e algoritmus je
kvalitnφ, nebo p°inejmenÜφm nejlepÜφ mo₧n², p°ichßzφ na °adu poupravit jeho k≤d,
tedy to, jak²mi prost°edky Object Pascalu je zapsßn. V ₧ßdnΘm p°φpad∞ to
neznamenß, ₧e by jste se m∞li pokouÜet optimalizovat ka₧diΦk² °ßdek svΘho
programu. PostaΦφ vylepÜit ty Φßsti, pro kterΘ je rychlost d∙le₧itß, nebo
identifikovat ty Φßsti, kterΘ jsou opravdu p°φliÜ pomalΘ. Velk² klam je to,
₧e optimalizace p°ichßzφ a₧ tehdy, kdy₧ je k≤d hotov². Mnohem lepÜφ a z
programßtorskΘho hlediska takΘ profesionßln∞jÜφ je nauΦit se psßt kvalitnφ a
rychl² k≤d u₧ od zaΦßtku. K tomu je ovÜem pot°eba se seznßmit se vÜemi pravidly
psanφ rychlΘho program∙, a prßv∞ to mß b²t hlavnφm tΘmatem tohoto Φlßnku.
Hlavnφ zßsady
V jednoduchosti je sφla
P°ekladaΦ (kompilßtor, compiler) Delphi
automaticky p°i p°eklßdßnφ tvΘho k≤du provßdφ n∞kterΘ zßsadnφ optimalizace,
obsahuje toti₧ vlastnφ optimizßtor (optimizΘr, optimizer). Ten se starß
nejen o jednoduchΘ v∞ci jako je t°eba odstra≥ovßnφ zbyteΦn²ch °ßdk∙ k≤du, jeho
hlavnφ pracφ je pochopit nßÜ "slo₧it²" k≤d a upravit jej tak, aby se dal co
nejefektivn∞ji p°elo₧it do spustitelnΘho souboru. Platφ pravidlo, ₧e Φφm je
k≤d jednoduÜÜφ, tφm ·sp∞Ün∞jÜφ optimizer je. DoporuΦuje se v jednΘ rutin∞
nepou₧φvat vφce ne₧ 5 prom∞nn²ch (variables). Nenφ takΘ radno provßd∞t b∞hem
jednΘ smyΦky p°φliÜ mnoho operacφ, to toti₧ Φasto vede k realokaci °φdφcφch nebo
pomocn²ch prom∞nn²ch. ╚asto je v²hodnΘ dlouhΘ smyΦky rozd∞lit do vφce
jednotliv²ch nebo v nich provßd∞nΘ operace p°emφstit do samostatnΘ funkce nebo
procedury (obecn∞ rutiny). P°φjemn²m vedlejÜφm efektem b²vß takΘ zv²Üenφ
Φitelnosti takovΘho k≤du.
Pou₧φvejte lokßlnφ prom∞nnΘ
Jen lokßlnφ prom∞nnΘ, tedy ty, kterΘ jsou
definovanΘ p°φmo v rutin∞, mohou b²t p°i jejφm spuÜt∞nφ p°evedeny do
procesorov²ch registr∙, co₧ je velmi v²znamnΘ pro rychlost. N∞kdy je v²hodnΘ
prom∞nnΘ do lokßlnφch prom∞nn²ch vynucen∞ zkopφrovat, zejmΘna pokud jsou pou₧ity
v n∞jakΘ smyΦce. To se t²kß hlavn∞ vlastnostφ t°φd (tedy t°eba
komponent). V²jimka z tohoto pravidla jsou pole (arrays) s elementy n∞kterΘho
jednoduchΘho typu (t°eba integer, byte, char,
atd.). Ty je n∞kdy v²hodnΘ za°adit mezi globßlnφ prom∞nnΘ, ale samoz°ejm∞ jen
tehdy, pokud to mß v rßmci celΘho programu n∞jak² v∞tÜφ smysl.
MalΘ mno₧stvφ parametr∙
Hodn∞ Φasto pou₧φvanΘ rutiny by nem∞ly mφt vφce
ne₧ t°i parametry, neb t°i je p°esn∞ poΦet prom∞nn²ch, kterΘ mohou b²t v jeden
Φas umφst∞ny do registru. Tφmto se rychlosti procesorov²ch registr∙ vyu₧ije na
maximum a optimizer dostane v∞tÜφ Üanci vylepÜit vßÜ k≤d. Metody t°φd vÜak majφ
skryt² parametr self, kter²m odkazujφ na
konkrΘtnφ instanci danΘ t°φdy, zb²vajφ tedy tak u₧ jen dva parametry k umφst∞nφ
do registr∙.
Ukazatele
Cennß technika je vyu₧φvat v²hod, kterΘ p°inßÜejφ ukazatele
(pointers). Jejich pou₧φvßnφ p°inßÜφ optimizeru mnoho informacφ o tvΘm
k≤du, ale to samoz°ejm∞ neznamenß abyste vÜechny objekty referencovali jako
ukazatele. Pokud definujete objekt t°eba takto
var Soubor : TObject;
Ukazatel : Pointer;
mo₧nß
ani nevφte, ₧e prom∞nnß Soubor je vlastn∞
ukazatel, ukazuje na urΦitΘ mφsto v pam∞ti a navφc urΦuje, jakß data jsou v
danΘm pam∞¥ovΘm ·seku zapsßny. Toho lzes v²hodou vyu₧φvat, a do prom∞nn²ch Pointer uklßdat reference na objekty jakΘhokoli typu.
begin
Soubor := TObject.Create;
Ukazatel := Soubor;
TObject(Ukazatel).Free;
end;
Pole
Pokud to jde, nepou₧φvejte dynamickß pole. Prßce s nimi je oproti
klasick²m (tedy statick²m) polφm o dost pomalejÜφ. Musφte-li je pou₧φt,
nezapomφnejte na funkce High or Length pro
zjiÜ¥ovßnφ velikosti pole. Ve skuteΦnosti fce High volß fci Length,
Length je proto doporuΦovan∞jÜφ. NezjiÜ¥ujte ale
opakovan∞ velikost pole t∞mito funkcemi, je rychlejÜφ jej zjistit jednou a
ulo₧it do lokßlnφ prom∞nnΘ. Jednou z mnoha vlastnostφ Delphi ulehΦujφcφch nßm
prßci jsou tzv. open arrays. P°φkladem jejich pou₧itφ budi₧ tento k≤d:
var MalaTabulka : Array[1..10] of byte;
VelkaTabulka : Array[1..100] of byte;
LibovolnaTabulka : Array of byte;
procedure VypisTabulku(Tabulka : array of byte);
var I : LongInt;
begin
for I := 0 to Length(X) - 1 do VypisHodnotu(Tabulka[i]);
end;
Jak vidφte,
parametr Tabulka neudßvß velikost p°edßvanΘho
pole. ProblΘmem tΘto pohodlnΘ v∞ci je, ₧e volßnφ procedury VypisTabulku vytvo°φ lokßlnφ kopii celΘho pole, co₧,
krom∞ faktu ₧e se tφm pl²tvß pam∞tφ, nenφ zrovna nejrychlejÜφ operace. V tomto
p°φpad∞ je vhodnΘ parametr Tabulka p°edznamenat
klφΦov²m slovem const nebo var, aby se tφm zabrßnilo vytvß°enφ lokßlnφ kopie.
JeÜt∞ je t°eba poznamenat, ₧e pou₧itφ const zabrßnφ programßtorovi ve zm∞n∞
celΘho pole, ne u₧ tak jeho jednotliv²ch element∙, tak₧e pozor.
KonkrΘtnφ metody optimalizace
Mno₧iny
Pokud pro p°idßvßnφ/odebφrßnφ Φlen∙ do/z mno₧iny (set)
pou₧φvßte klasickΘ operßtory (+ a -), v∞zte, ₧e o n∞co rychlejÜφ je prßce s
rutinami Include a Exclude.
SmyΦka for
Jedna z nejΦast∞jÜφch konstrukcφ v Object Pascalu a zßrove≥
nejslo₧it∞jÜφ prßciΦka pro optimizer. Nap°φklad smyΦka
for I := M to N do
A[i] := A[i] + B[i]
Bude
optimizerem p°evedena do
PA := @A[m];
PB := @B[m];
Pocitadlo := M - N + 1;
if Pocitadlo > 0 then
repeat
PA^ := PA^ + PB^;
Inc(PA);
Inc(PB);
Dec(Pocitadlo);
Until Pocitadlo = 0;
Existujφ jinΘ
mo₧nosti, ale tato je nejΦast∞jÜφ, a je z nφ dob°e vid∞t, kolik dodateΦnΘ prßce
zdßnliv∞ tak jednoduchß v∞c, jako je for smyΦka,
vy₧aduje. Platφ tedy, ₧e Φφm jednoduÜÜφ svou smyΦku ud∞lßte, tφm rychlejÜφ bude,
a proto m∙₧e b²t n∞kdy lepÜφ slo₧itΘ smyΦky rozd∞lit do n∞kolika menÜφch.
Interfacy
Nevy₧adujφ p°φliÜ mnoho optimalizaΦnφ ·dr₧by. Obsahujφ v sob∞
tzv. reference counter (doslova p°elo₧eno jako ΦφtaΦ referencφ,
odkaz∙), kter² zajiÜ¥uje uvoln∞nφ pam∞ti okupovanΘ jednou instancφ. Poka₧dΘ,
kdy₧ je dan² interface nap°φklad p°i°azen n∞kterΘ prom∞nnΘ, zv²Üφ se RC o jeden,
a analogicky se snφ₧φ v moment∞ kdy je n∞kter² odkaz na tento objekt zruÜen.
Dosßhne-li RC nuly, je i objekt zruÜen. Proto si dßvej pozor a vyh²bej se znßmΘ
technice, kterß vy₧aduje opakovanΘ ruÜenφ a znovuvytvß°enφ interface.
Pam∞¥ovΘ zarovnßvßnφ
Pou₧φvejte 32-bitovΘ prom∞nnΘ kdekoli a kdykoli je
to mo₧nΘ. SouΦasnΘ procesory jsou u₧ n∞jak² ten pßtek 32-bitovΘ a prßce s 4
bytov²mi bloky dat je proto nejrychlejÜφ. Pro celoΦφselnΘ prom∞nnΘ to tedy
nejΦast∞ji budou DWord, Longint nebo t°eba Cardinal. Kdy₧ musφte pou₧φvat prom∞nnΘ o jinΘ
velikosti (nap°φklad z d∙vod∙ kompatibility), zm∞≥te je prost²m p°i°azenφm
nejd°φve v 32-bitovΘ, a a₧ kdy₧ je pot°eba, zp∞t na p∙vodnφ velikost.
Zrychlovßnφ smyΦek
je jedna z nejΦast∞jÜφch optimizaΦnφch technik. V
angliΦtin∞ se tomu °φkß loop unrolling. Z vlastnφch zkuÜenostφ ale m∙₧u
°φct, ₧e tato technika je ·Φinnß jen u relativn∞ mal²ch smyΦek a to navφc jen
p°i pou₧itφ konstrukce while. Viz. Tento
k≤d:
I := 0;
while I < Count do
begin
Data[I] := Data[I] + 1;
Inc(I);
end;
Tato smyΦka m∙₧e
b²t snadno zrychlena nßsledujφcφm zp∙sobem
I := 0;
while I < Count do
begin
Data[I] := Data[I] + 1;
Data[I + 1] := Data[I + 1] + 1;
Inc(I, 2);
end;
Tφmto se snφ₧φ
poΦet pr∙chod∙ while smyΦkou. ProblΘm tΘto
techniky tkvφ v pot°eb∞ dodateΦnΘho k≤du v p°φpad∞, ₧e Count je lichß hodnota a nenφ tedy d∞litelnß dv∞ma, a
takΘ v tom, ₧e se tφm komplikuje Φitelnost k≤du. Poznßmka: Nemusφte se
samoz°ejm∞ omezovat jen na dv∞ operace uvnit° smyΦky. Typicky se jich vÜak pro
tento ·Φel nedoporuΦuje pou₧φvat vφce ne₧ 4, a to nejen kv∙li p°ehlednosti, ale
zejmΘna kv∙li p°φliÜnΘ "slo₧itosti" k≤du pro optimizer. DalÜφ p°ipomφnkou je, ₧e
vedle while se dß podobn∞ dob°e zrychlit i
konstrukce repeat.
DalÜφ pravidla pro smyΦky
Uvnit° smyΦky pou₧φvejte co nejmΘn∞ podmφnek
if, zvlßÜt∞ kdy₧ takovß podmφnka volß jinΘ
rutiny. TakΘ se osv∞dΦuje omezit mno₧stvφ °φdφcφch podmφnek pro konstrukce while a repeat.
Samoz°ejm∞ se ale ve v∞tÜin∞ p°φpad∙ slo₧it∞jÜφm podmφnkßm nevyhneme.
U₧φvejte v²hod Exit, Break a Continue
P°esto₧e tyto vlastnosti Object
Pascalu b²vajφ pova₧ovßny za znßmku ÜpatnΘho a nekvalitnφho k≤du, majφ svΘ
mφsto, a to hlavn∞ tam, kde je d∙le₧itß rychlost. Pro ostatnφ p°φpady se
doporuΦuje pou₧φvat standardnφ konstrukce s podmφnkami a bloky beginàend.
for a while
Tam, kde je p°edem znßm² poΦet pr∙chod∙ smyΦky, se
nejΦast∞ji pou₧φvajφ konstrukce for nebo while. Typicky bude zvolena spφÜe for, pro svou jednoduchost. V n∞kter²ch p°φpadech je
ale while o n∞co efektivn∞jÜφ a optimizer takΘ
n∞kdy, jak u₧ jsem ukßzal na p°φkladu v²Üe, konstrukce for p°evßdφ na konstrukce while. Platφ, ₧e tam, kde jsou v t∞le smyΦky pou₧ita
pole s jednotliv²mi elementy o velikosti 1, 2, 4, nebo 8 byt∙, nebo nejsou pole
pou₧ita v∙bec, je pou₧itφ while v²konn∞jÜφ. Ale
tam, kde se objevujφ zejmΘna vφcerozm∞rnß pole nebo pole s elementy o jinΘ
velikosti, b²vß rychlejÜφ naopak for.
Existuje vÜak jedna v²jimka - kdy₧ nenφ v t∞le smyΦky nikde pou₧ita °φdφcφ
prom∞nnß a jde vßm tedy jen o urΦit² poΦet provedenφ n∞jakΘho k≤du, je for efektivn∞jÜφ mo₧nostφ. Poznßmka: Zajφmavou
vlastnostφ while v tomto p°φpad∞ je, ₧e dosahuje
v∞tÜφho v²konu pokud °φdφcφ prom∞nnß nab²vß negativnφch hodnot a b∞hem
jednotliv²ch pr∙b∞h∙ sm∞°uje sm∞rem k nule.
case
Implementace case je kompilßtorem
pom∞rn∞ dob°e zvlßdnutß, i tato konstrukce se vÜak dß dßle optimalizovat.
NejlΘpe si to ukß₧eme na p°φkladu:
Je zde vid∞t
pom∞rn∞ velikß mezera mezi hodnotami 107 a 200, co₧ brßnφ optimizeru v
efektivn∞jÜφ optimalizaci k≤du. Upravφme jej jako:
case x of
100..107 :
case x of
100 :DoSomething1;
101 :DoSomethingFrequently2;
102 :DoSomething3;
103 :DoSomething4;
104 :DoSomething5;
105 :DoSomething6;
106 :DoSomething7;
107 :DoSomething8;
end;
200..210 :
case x of
200 :DoSomething9;
201 :DoSomething10;
202 :DoSomething11;
203 :DoSomething12;
204 :DoSomething13;
205 :DoSomething14;
206 :DoSomething15;
207 :DoSomething16;
end;
end;
Kdy₧ vφte,
₧e n∞kterΘ hodnoty v case se objevujφ Φast∞ji, je
dobrΘ je umφstit jeÜt∞ p°ed samotnou konstrukci:
if X = 101 then
DoSomethingFrequently2 else
case X of
100 :DoSomething1;
102 :DoSomething3;
103 :DoSomething4;
104 :DoSomething5;
105 :DoSomething6;
106 :DoSomething7;
107 :DoSomething8;
end;
V²ΦtovΘ typy
Pokud to jde, vyhn∞te se v²Φtov²m typ∙m. Nap°φklad typ
TRozsah = 0-6000 bude ulo₧en intern∞ jako Word, co₧, jak jsme si u₧ °ekli, je neefektivnφ.
Podobn∞ bude mno₧ina o dejme tomu 250 elementech ulo₧ena jako byte.
CeloΦφselnΘ nßsobenφ
Nßsobenφ celoΦφseln²ch typ∙ je doslova utrpenφm pro
starÜφ procesory jako je 80486, Pentium Φi Pentium Pro. Situace se zlepÜila s
p°φchodem Pentia II. Kompilßtor si toho je v∞dom a vÜude, kde to jen trichu jde,
nahrazuje takovΘ nßsobenφ jin²mi operacemi. Pokud tedy pφÜete k≤d, pro kter² je
rychlost kritickß, musφte uva₧ovat i cφlov² systΘm a optimalizaci tomu
p°izp∙sobit.
Zkracovßnφ podmφnek
Porovnßvßte-li prom∞nnou s vφce jednoduch²mi typy
najednou, je p°ehledn∞jÜφ a efektivn∞jÜφ pou₧φt mφsto nich v²Φet. NejlepÜφ bude
ukßzat si to na p°φkladu:
if (((C > = 'a') and (C < = 'z')) or ((C > = '0') and (C < = '9'))) then
DoSomething;
// zm∞≥te na
if C in ['0'..'9', 'a'..'z'] then
DoSomething;
// p°φpadn∞ na
case C of
'0'..'9', 'a'..'z' : DoSomething;
End;
DlouhΘ °et∞zce (AnsiString)
Nepou₧φvejte asociace pro poΦßteΦnφ
vynulovßnφ °et∞zce na zaΦßtku rutiny. AnsiString
(a tedy tφm pßdem string p°i standardnφm
nastavenφ Delphi) je automaticky p°i svΘ inicializaci nastaven na prßzdn²
°et∞zec. Pou₧φvßnφ operacφ typu S := S + S2[I] ve
smyΦkßch zp∙sobuje dodateΦnou a pomalou alokaci pam∞ti p°i ka₧dΘm takovΘm
zv∞tÜenφ stringu. Abyste tomu p°edeÜli, zavolejte nejd°φve SetLength(S, Nova_Delka). Toto je bohu₧el velmi Φastß
chyba programßtor∙, zp∙sobenß jen tφm, ₧e zv∞tÜovßnφ a zmenÜovßnφ dynamick²ch
°et∞zc∙ je v Delphi obstarßvßno automaticky.
Procedura Copy
╚astokrßt je k vid∞nφ, ₧e programßto°i pro odstran∞nφ
urΦitΘ Φßsti °et∞zce pou₧φvajφ funkci Copy.
Nap°φklad odstran∞nφ prvnφch 20ti znak∙ m∙₧e vypadat takto:
S := Copy(S, 21, Length(S) - 20); // nesprßvn∞!!!
Delete(S, 1, 20); // mnohem, mnohem rychlejÜφ
string jako PChar
Pot°ebujete-li z n∞jak²ch d∙vod∙ na prom∞nou
dynamickΘho °et∞zce (definovanou t°eba jako S)
odkazovat jako na PChar, mßte t°i ekvivalentnφ
mo₧nosti, p°iΦem₧ nejrychlejÜφ je ta poslednφ. Prvnφ je pou₧φt p°φmo PChar(S). Druhß tkvφ v referenci na prvnφ znak °et∞zce,
tedy @S[1]. Tou poslednφ je ukßzat na °et∞zec
p°φmo ukazatelem, pou₧φt tedy klasick² Pointer:
Pointer(S).
DatovΘ typy s plovoucφ Φßrkou
Tyto floating types poskytujφ velikou
flexibilitu, ale takΘ mohou zabrat mnoho pam∞ti. Stßle platφ, 32-bitovΘ typy
jsou nejrychlejÜφ. Pov∞tÜinou ale pou₧φvßme tyto typy takΘ pro jejich Φasto
velik² rozsah, proto n∞kdy je nutnΘ on∞ch 32-bit∙ nedodr₧et. Prvnφ pravidlo
znφ, pou₧φvejte Extended jen kdy₧ je to nezbytn∞
nutnΘ. Jejich velikost (10 byt∙) je zejmΘna pro zßkladnφ operace (p°edevÜφm +,
-, *) vysoce neefektivnφ, a projevφ se to ve ztrßt∞ v²konu. Dßle, sna₧te se
tyto typy nemixovat - pokud u₧ musφte pou₧φvat n∞kterΘ z t∞chto "desetinn²ch"
typ∙, dr₧ se jen jednoho z nich. V p°φpad∞, ₧e definujete konstantu n∞kterΘho
typu s plovoucφ Φßrkou, ud∞lejte z nφ konstantu s p°i°azen²m datov²m typem, a to
Single nebo Double (const
X : Single;). Pokud to neud∞lßte, bude ulo₧ena jako Extended, co₧ je siln∞ nepraktickΘ. Namφsto Trunc pou₧φvejte Round.
Ta je na Pentiu II p°ibli₧n∞ 2,74x rychlejÜφ. Vyh²bejte se d∞lenφ kdekoli to
jen jde, zejmΘna uvnit° smyΦek. Je asi 35x pomalejÜφ ne₧ ostatnφ b∞₧nΘ operace
(!).
Poslednφ rada ohledn∞ t∞chto typ∙ °φkß, nepou₧φvejte je jako v²stup
funkce. Mφsto toho definujte proceduru a dan² v²stup vracejte pomocφ var:
function Fnc(X : Double) : Double;
begin
Result := X * 1.8 + 2;
end;
// po ·prav∞
procedure Fnc(X : Double; var Result : Double)
begin
Result := X * 1.8 + 2;
end;